package cs236369.webserver;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Scanner;
import java.util.TreeMap;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.FileEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpDateGenerator;
import org.apache.http.protocol.HttpRequestHandler;

import com.sun.jdi.Method;

import cs236369.webserver.SessionRepository.Session;
import cs236369.webserver.DTOs.MimeMapping;
import cs236369.webserver.dynamicLoad.JavaCompile;

public class TSPEngine implements HttpRequestHandler {
	
	private static final String cookieName = "HW4SessionID";
	private static final SessionRepository repo = new SessionRepository(cookieName);
	private final String jrePath;
	private String baseDir;
	private Collection<MimeMapping> mimes;
	private final String tmpClassName;
	private final String separator = System.getProperty("line.separator");
	
	public TSPEngine(Properties p) {
		this.jrePath = p.getProperty("jre_path");
		this.tmpClassName = "Z" + Long.toString((new Date()).getTime());
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void handle(HttpRequest request, HttpResponse response,
			HttpContext context) throws HttpException, IOException {
		
		if(response == null || request == null) {
			throw new NullPointerException("In TSPEngine response or request is null");
		}
		
		baseDir = (String) context.getAttribute("baseDir");
		mimes = (Collection<MimeMapping>) context.getAttribute("mimes");
		Map<String,String> queryParams = new TreeMap<String,String>();
		String path = getPathAndParams(request,queryParams);
		
		String fps = File.separator;
		File input = new File(baseDir,path);
		File output = new File(baseDir, fps + "tmp" + fps + tmpClassName + ".java");
		(new File(baseDir,fps+"tmp")).mkdir();
		output.createNewFile();
		
		BufferedReader bufRead = new BufferedReader(new FileReader(input));
		FileWriter fileWrite = new FileWriter(output);
		
		openClassAndMethod(fileWrite);
		
		String line;
		while (null != (line = bufRead.readLine())) {
			for(String result : processLine(line)) {
				fileWrite.write(result+separator);
			}
		}
		fileWrite.write("}"+separator+"}"+separator);
		fileWrite.close();
		bufRead.close();
		
		File html = new File(baseDir,fps+"tmp"+fps+tmpClassName+".html");
		html.createNewFile();
		html.deleteOnExit();
		
		PrintStream htmlStream = new PrintStream(html);
		Session sess = repo.getSession(extractSessionId(request));
		
		JavaCompile jc = new JavaCompile();
		
		try {
			Class<?> tmpClass = jc.compileAndLoad(baseDir + fps + "tmp" + fps + tmpClassName +".java", tmpClassName);
			Object o = tmpClass.newInstance();
			java.lang.reflect.Method m = tmpClass.getDeclaredMethod("generateHTML", PrintStream.class,Map.class,Session.class);
			m.invoke(o,new Object[] {htmlStream,queryParams,sess});
		} catch (Exception e) {
			setError(response,HttpStatus.SC_INTERNAL_SERVER_ERROR,"couldn't load tsp");
			e.printStackTrace();
			return;
		} 
		
		htmlStream.close();
		FileEntity fen = new FileEntity(html,mimeLookup(input));
		
		response.setStatusLine(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
		response.setEntity(fen);
		response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(html.length()));
		output.delete();
	}
	
	private long extractSessionId(HttpRequest request) {
		if(!request.containsHeader("Cookie"))
			return 0;
		else {
			for(Header h : request.getHeaders("Cookie")) {
				if(h.getValue().contains(cookieName)) {
					int start = h.getValue().indexOf(cookieName+"=") + cookieName.length()+1;
					int finish = h.getValue().indexOf(";",start);
					return Long.parseLong(h.getValue().substring(start, finish));
				}
			}
		}
		return 0;
	}

	private void openClassAndMethod(FileWriter fileWrite) throws IOException {
		fileWrite.write("import java.util.Map;"+separator);
		fileWrite.write("import java.io.PrintStream;"+separator);
		fileWrite.write("import cs236369.webserver.SessionRepository.Session;"+separator);
		//open class
		fileWrite.write("public class " + tmpClassName + " {" + separator);
		//constructor
		fileWrite.write("public " + tmpClassName + "(){}" + separator);
		//method
		fileWrite.write("public void generateHTML(PrintStream out, Map<String,String> params, Session session) {" +separator);
		
	}
	
	//This is ugly... but yeah
	private boolean inSpecialSyntax = false;
	private List<String> processLine(String line) {
		ArrayList<String> $ = new ArrayList<String>();
		StringBuffer sbuff = new StringBuffer();
		int i=0;
		int lefty = line.indexOf("<?");
		int righty = line.indexOf("?>");
		if(righty == -1 && lefty == -1) {
			if(inSpecialSyntax)
				$.add(line);
			else
				$.add("out.print(\""+line.replaceAll("\"", "\\\"")+"\");");
			return $;
		}
		if(righty<lefty && righty != -1) {
			$.add(line.substring(0, righty));
			i = righty + 2;
		}
		boolean sawSpecial = false;
		boolean wentInFor = true;
		while(i<line.length() && wentInFor) {
			wentInFor = false;
			for(; i<line.indexOf("<?", i) ; i++) {
				sbuff.append(line.charAt(i));
				sawSpecial = true;
			}
			if(sawSpecial) {
				$.add("out.print(\""+sbuff.toString().replaceAll("\"", "\\\"")+"\");");
				sbuff.delete(0, sbuff.length());
				i+=2;
				inSpecialSyntax = true;
				wentInFor = true;
			}
			sawSpecial = false;
			for(; i<line.indexOf("?>",i) ; i++) {
				sbuff.append(line.charAt(i));
				sawSpecial = true;
			}
			if(sawSpecial) {
				$.add(sbuff.toString());
				sbuff.delete(0, sbuff.length());
				i+=2;
				inSpecialSyntax = false;
				wentInFor = true;
			}
			sawSpecial = false;
		}
		if(i < line.length()) {
			if(inSpecialSyntax)
				$.add(line.substring(i));
			else
				$.add("out.print(\""+line.substring(i).replaceAll("\"", "\\\"")+"\");");
		}
		return $;
	}
	
	private void setError(HttpResponse response, int statusCode, String reason) {
		HttpDateGenerator dgen = new HttpDateGenerator();
		response.setStatusLine(new ProtocolVersion("HTTP", 1, 1), statusCode, reason);
		response.setHeader(HTTP.CONTENT_LEN, "0");
		response.setHeader(HTTP.DATE_HEADER,dgen.getCurrentDate());
		response.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
	}
	
	private String mimeLookup(File f) throws IOException {
		String res = "";
		synchronized (mimes) {
			for(MimeMapping mm : mimes) {
				if(f.getCanonicalPath().endsWith(mm.getExtension())) {
					res = mm.getMimeType();
					break;
				}
			}
		}
		return res;
	}
	
	private String getPathAndParams(HttpRequest req, Map<String,String> m) {
		String query = null;
		String path = null;
		try {
			// for parsing's sake, assume it's an absolute URL
			URL url = new URL(req.getRequestLine().getUri());
			query = url.getQuery();
			path = url.getPath();
		} catch(MalformedURLException e) {
			// so it was a relative URL, no biggy
			String[] sp = req.getRequestLine().getUri().split("\\?");
			path = sp[0];
			if(sp.length > 1)
				query = sp[1];
			query = null;
		}
		
		if(req.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH).equals("POST"))
			if(req instanceof HttpEntityEnclosingRequest) {
				HttpEntityEnclosingRequest req1 = (HttpEntityEnclosingRequest) req;
				ByteArrayOutputStream out = new ByteArrayOutputStream();
				try {
					req1.getEntity().writeTo(out);
					query = out.toString();
				} catch (IOException e) {
				}
			}
		
		queryIntoMap(query, m);
		return path;
	}

	private void queryIntoMap(String query, Map<String, String> m) {
		if(query != null && m != null)
			for(String param : query.split("&")) {
				String[] p = param.split("=");
				try {
					String key = URLDecoder.decode(p[0], "UTF-8");
					String value = URLDecoder.decode(p[1], "UTF-8");
					m.put(key, value);
				} catch(UnsupportedEncodingException e) {
					//TODO Tough Luck
				}
			}
	}

}
